/************************************************************************
* material.c
* voxelands - 3d voxel world sandbox game
* Copyright (C) Lisa 'darkrose' Milne 2016 <lisa@ltmnet.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
************************************************************************/

#include "common.h"
#include "graphics.h"
#include "list.h"
#include "array.h"

#include <string.h>

static struct {
	material_t *materials;
	int ids;
	GLuint current_texture[8];
	uint8_t culling;
	uint8_t blended;
	GLenum blend_mode;
} mat_data ={
	NULL,
	0,
	{0,0,0,0,0,0,0,0},
	0,
	0,
	GL_ZERO
};

material_t *mat_find(char* name)
{
	material_t *m = mat_data.materials;

	while (m) {
		if (!strcmp(m->name,name))
			return m;
		m = m->next;
	}

	return m;
}

/* create a new blank material */
material_t *mat_create()
{
	material_t *mat = malloc(sizeof(material_t));
	mat->id = mat_data.ids++;
	mat->name[0] = 0;
	mat->options = MATOPT_BFCULL;
	array_init(&mat->textures,ARRAY_TYPE_PTR);
	mat->next = NULL;
	mat->shininess = 1.0;
	mat->reflectivity = 0.0;
	mat->bumpiness = 0.0;

	mat_data.materials = list_push(&mat_data.materials,mat);

	return mat;
}

/* free a material */
void mat_free(material_t *mat)
{
	texture_t *tex;
	if (!mat)
		return;

	mat_data.materials = list_remove(&mat_data.materials,mat);

	while ((tex = array_pop_ptr(&mat->textures))) {
		tex_free(tex);
	}

	array_free(&mat->textures,0);

	free(mat);
}

/* create a material from a texture */
material_t *mat_from_tex(texture_t *tex)
{
	material_t *mat = mat_create();
	array_push_ptr(&mat->textures,tex);
	strcpy(mat->name,tex->name);

	return mat;
}

/* create a material from pixel data */
material_t *mat_from_pixels(image_t *p)
{
	texture_t *tex;
	material_t *mat = mat_create();
	tex = tex_from_pixels(p);
	array_push_ptr(&mat->textures,tex);
	strcpy(mat->name,tex->name);

	return mat;
}

/* create a material from an image */
material_t *mat_from_image(char* type, char* file)
{
	texture_t *tex;
	material_t *mat;

	tex = tex_from_image(type,file);
	if (!tex)
		return NULL;

	mat = mat_create();
	array_push_ptr(&mat->textures,tex);

	strcpy(mat->name,tex->name);

	return mat;
}

/* combine size images into a texture cube */
material_t *mat_from_box(char* front, char* back, char* left, char* right, char* top, char* bottom)
{
	texture_t *tex;
	material_t *mat;

	tex = tex_from_box(front,back,left,right,top,bottom);
	if (!tex)
		return NULL;

	mat = mat_create();
	array_push_ptr(&mat->textures,tex);

	strcpy(mat->name,tex->name);

	return mat;
}

/* create a material from a colour */
material_t *mat_from_colour(colour_t *c)
{
	char buff[256];
	texture_t *tex;
	material_t *mat;
	snprintf(buff,256,"rgba-%d-%d-%d-%d",c->r,c->g,c->b,c->a);

	tex = tex_from_rgba(1,1,c->r,c->g,c->b,c->a);
	if (!tex)
		return NULL;

	mat = mat_create();
	array_push_ptr(&mat->textures,tex);

	strcpy(mat->name,buff);

	return mat;
}

/* add a second texture to a material from an image */
int mat_add_image(material_t *mat, char* type, char* file)
{
	texture_t *tex;
	tex = tex_from_image(type,file);
	if (!tex)
		return 1;

	array_push_ptr(&mat->textures,tex);

	return 0;
}

/* add a new texture to a material */
int mat_add_tex(material_t *mat, texture_t *tex)
{
	if (!tex)
		return 1;

	array_push_ptr(&mat->textures,tex);

	return 0;
}

/* add a new texture from a colour to a material */
int mat_add_colour(material_t *mat, colour_t *c)
{
	texture_t *tex;
	if (!c)
		return 1;

	tex = tex_from_rgba(1,1,c->r,c->g,c->b,c->a);
	if (!tex)
		return 1;

	array_push_ptr(&mat->textures,tex);

	return 0;
}

static void mat_apply_opts(uint32_t options)
{
	/* back face culling */
	if ((options&MATOPT_BFCULL) == MATOPT_BFCULL) {
		if (!mat_data.culling) {
			glEnable(GL_CULL_FACE);
			glCullFace(GL_BACK);
			mat_data.culling = 1;
		}
	}else if (mat_data.culling) {
		glDisable(GL_CULL_FACE);
		mat_data.culling = 0;
	}

	/* blending */
	if ((options&MATOPT_ADDITIVE_BLEND) == MATOPT_ADDITIVE_BLEND) {
		if (!mat_data.blended) {
			glEnable(GL_BLEND);
			glEnable(GL_LINE_SMOOTH);
			mat_data.blended = 1;
		}
		if (mat_data.blend_mode != GL_ONE) {
			glBlendFunc(GL_SRC_ALPHA, GL_ONE);
			mat_data.blend_mode = GL_ONE;
		}
	}else if ((options&MATOPT_ALPHA_BLEND) == MATOPT_ALPHA_BLEND) {
		if (!mat_data.blended) {
			glEnable(GL_BLEND);
			glEnable(GL_LINE_SMOOTH);
			mat_data.blended = 1;
		}
		if (mat_data.blend_mode != GL_ONE_MINUS_SRC_ALPHA) {
			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
			mat_data.blend_mode = GL_ONE_MINUS_SRC_ALPHA;
		}
	}else if (mat_data.blended) {
		glDisable(GL_BLEND);
		mat_data.blended = 0;
		mat_data.blend_mode = GL_ZERO;
	}

	if ((options&MATOPT_SDF_ALPHA) == MATOPT_SDF_ALPHA) {
		if (opengl_has_psdf()) {
			shader_uniform_float(NULL,"alphafunc",2.0);
		}else{
			shader_uniform_float(NULL,"alphafunc",1.0);
		}
	}else if ((options&MATOPT_ALPHA_TEST) == MATOPT_ALPHA_TEST) {
		shader_uniform_float(NULL,"alphafunc",1.0);
	}else{
		shader_uniform_float(NULL,"alphafunc",0.0);
	}
}

static void mat_bind(GLuint tex, GLenum type, uint8_t slot)
{
	char buff[32];
	if (slot < 0 || slot > 7)
		return;

	if (mat_data.current_texture[slot] == tex)
		return;

	switch (slot) {
	case 0:
		glActiveTexture(GL_TEXTURE0);
		break;
	case 1:
		glActiveTexture(GL_TEXTURE1);
		break;
	case 2:
		glActiveTexture(GL_TEXTURE2);
		break;
	case 3:
		glActiveTexture(GL_TEXTURE3);
		break;
	case 4:
		glActiveTexture(GL_TEXTURE4);
		break;
	case 5:
		glActiveTexture(GL_TEXTURE5);
		break;
	case 6:
		glActiveTexture(GL_TEXTURE6);
		break;
	case 7:
		glActiveTexture(GL_TEXTURE7);
		break;
	default:
		return;
	}

	snprintf(buff,32,"texture%u",slot);

	glBindTexture(type,tex);

	mat_data.current_texture[slot] = tex;

	shader_uniform_int(NULL,buff,slot);
}

/* bind a texture and apply options */
int mat_bind_with_opts(GLuint tex, uint32_t options)
{
	uint8_t i;
	mat_bind(tex,GL_TEXTURE_2D,0);
	mat_apply_opts(options);

	for (i=1; i<8; i++) {
		mat_bind(0,GL_TEXTURE_2D,i);
	}


	return 0;
}

/* use a material */
void mat_use(material_t *mat, shader_t *shader)
{
	if (mat->textures.length) {
		texture_t *tex;
		int i;
		for (i=0; i<8; i++) {
			tex = array_get_ptr(&mat->textures,i);
			if (tex) {
				if (tex->state != 1)
					tex_generate(tex);
				mat_bind(tex->glid,tex->type,i);
			}else if (mat_data.current_texture[i]) {
				mat_bind(0,GL_TEXTURE_2D,i);
			}
		}
		mat_apply_opts(mat->options);
	}else{
		mat_bind_with_opts(0,mat->options);
	}

	/* specular values */
	shader_uniform_float(shader,"shininess",mat->shininess);
	shader_uniform_float(shader,"reflectivity",mat->reflectivity);
	shader_uniform_float(shader,"bumpiness",mat->bumpiness);
}

/* set the shininess and reflectivity of a material */
void mat_shininess(material_t *mat, float shininess, float reflectivity)
{
	mat->shininess = shininess;
	mat->reflectivity = reflectivity;
}

/* set the bumpiness (for bump mapping) of a material */
void mat_bumpiness(material_t *mat, float bumpiness)
{
	mat->bumpiness = bumpiness;
}

/* set the name of a material */
void mat_name(material_t *mat, char* name)
{
	strncpy(mat->name,name,256);
}

/* set the options for a material, return the previous options */
uint32_t mat_options(material_t *mat, uint32_t opt)
{
	uint32_t o = mat->options;
	if (opt != MATOPT_GET)
		mat->options = opt;
	return o;
}
